package be.neo4reactome.queries;


import be.neo4reactome.importer.Importer;
import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;

import java.util.*;

import static org.neo4j.helpers.collection.MapUtil.map;

/**
 * Query class using the cypher query language.
 * @author Thilo Muth
 *
 */
public class CypherQuery {

    private final ExecutionEngine engine;

    public CypherQuery(GraphDatabaseService graphdb) {
        engine = new ExecutionEngine(graphdb);
    }

    public ExecutionResult getFirstNode() {
        return engine.execute( "start n=node(1) return n" );
	}
	
	public ExecutionResult getProteinByAccession(String accession) {
        return engine.execute("START protein=node:proteins(accession = {accession}) return protein", params(accession));
	}
	
	public ExecutionResult getAllNodes() {
        return engine.execute( "start n=node(*) return n limit 10" );
	}
	
	
	public ExecutionResult getInteractingProteins(String accession) {
        return engine.execute("START protein=node(accession = {accession}) " +
                              "MATCH (protein)-->(interactionprotein) " +
                              "RETURN interactionprotein", params(accession));
	}

    private Map<String, Object> params(String accession) {
        return map("accession",accession);
    }

    public ExecutionResult getRelationshipsFromProtein() {
        return engine.execute("START protein=node(1) " +
                              "MATCH (protein)-[rel]->() " +
                              //"WHERE follower.name =~ 'S.*' " +
                              "RETURN rel" );
	}
	
	public ExecutionResult getInteractionProteins(String accession, Importer.InteractionType type) {
        return engine.execute("START protein=node:proteins(accession = {accession}) " +
                              "MATCH (protein)-[:" + type.name() + "]->(proteinB) " +
                              "RETURN proteinB" , params(accession));
	}
	
	public ExecutionResult getAllProteinsForPathway(String pathwayName) {
        return engine.execute("START protein=node(*) " +
                              "MATCH (protein)-[:INVOLVED_IN]->(pathway) " +
                              "WHERE pathway.pathdesc='" + pathwayName + "' " +
                              "RETURN protein" );
	}
	
	public ExecutionResult getAllPathwaysForProtein(String accession) {
		return engine.execute("START protein=node:proteins(accession = {accession}) " +
												"MATCH (protein)-[:INVOLVED_IN]->(pathway) " +
												"RETURN pathway",params(accession) );
	}
	
	public ExecutionResult getAllPathwaysForProteins(String proteinA, String proteinB) {
        return engine.execute("START proteinA=node:proteins(accession = {proteinA}), " +
                                    "proteinB=node:proteins(accession = {proteinB}) " +
                              "MATCH (proteinA)-[:INVOLVED_IN]->(pathway)<-[:INVOLVED_IN]-(proteinB) " +
                              "RETURN pathway" ,
                              map("proteinA", proteinA, "proteinB", proteinB));
	}
	
	public ExecutionResult getSharedInteractionProteins(String proteinA, String proteinB) {
        return engine.execute("START proteinA=node:proteins(accession = {proteinA}), " +
                                    "proteinB=node:proteins(accession = {proteinB}) " +
                        "MATCH (proteinA)<-[:DIRECT_COMPLEX|INDIRECT_COMPLEX|NEIGHBOURING_REACTION]->(proteinC)" +
                               "<-[:DIRECT_COMPLEX|INDIRECT_COMPLEX|NEIGHBOURING_REACTION]->(proteinB) " +
                            "RETURN proteinC",
                        map("proteinA",proteinA,"proteinB",proteinB));
	}

    public ExecutionResult getAllNodesOnPath(String proteinA, String proteinB) {
        return engine.execute("START proteinA=node:proteins(accession = {proteinA}) " +
                "MATCH path=(proteinA)-[*..5]->(proteinB) " +
                "RETURN NODES(path)",
                map("proteinA",proteinA,"proteinB",proteinB));
    }
	
	 
	
	public ExecutionResult getSimilarInteractionProteins(int from, int to) {
        Collection<Long> range = limitRange(from, to);
        return engine.execute("START proteinA=node({range}), proteinB=node({range}) " +
                              "MATCH (proteinA)<-[:NEIGHBOURING_REACTION]->(proteinC)<-[:NEIGHBOURING_REACTION]->(proteinB)" +
                              "RETURN proteinA" , map("range",range));
	}

    private static Collection<Long> limitRange(int from, int to) {
        final Collection<Long> result = new ArrayList<Long>(to - from + 1);
        for (int i=from;i<=to;i++) result.add((long)i);
        return result;
    }

    public static void printResult(String msg, ExecutionResult result, String column) {
        System.out.println(msg);
        Iterator<Object> columnAs = result.columnAs(column);
        while (columnAs.hasNext()) {
            final Object value = columnAs.next();
            if (value instanceof Node) {
                Node n = (Node)value;
                for (String key : n.getPropertyKeys()) {
                    System.out.println("{ " + key + " : " + n.getProperty(key)	+ "; id: " + n.getId() + " } ");
                }
            } else {
                System.out.println("{ " + column + " : " + value + " } ");
            }
        }
    }

    public static void printRelationship(String msg, ExecutionResult result, String column) {
        System.out.println(msg);
        Iterator<Object> columnAs = result.columnAs(column);
        while (columnAs.hasNext()) {
            Relationship n = (Relationship) columnAs.next();
            for (String key : n.getPropertyKeys()) {
                System.out.println("{ " + key + " : " + n.getProperty(key)	+ " } ");
            }
        }
    }
}
